﻿using System;
using System.Collections.Generic;
using System.Configuration;
using System.Linq;
using Microsoft.Azure.ActiveDirectory.GraphClient;
using Microsoft.Azure.ActiveDirectory.GraphClient.Extensions;
using ProcessFile;

namespace O365LicMgmt
{
    class Program
    {

        private static ActiveDirectoryClient adClient;

        static void Main(string[] args)
        {

            GraphHelper.Constants.TenantName = ConfigurationManager.AppSettings.Get("tenant");
            GraphHelper.Constants.ClientId = ConfigurationManager.AppSettings.Get("clientId"); 
            GraphHelper.Constants.ClientSecret = ConfigurationManager.AppSettings.Get("clientSecret"); 

            adClient = ActiveDirectoryClientHelper.CreateActiveDirectoryClient();

            //printing the current skus for the Office365 tenant
            PrintSubscribedSkus(adClient);

            //example of printing a user's license
            //PrintUserLicense(adClient, "teststudent@CriterCollege.onmicrosoft.com");

            Process();

            Console.WriteLine("\nProgram Completed - Press any key to exit");
            Console.ReadKey();
        }


        private static void Process()
        {

            var mtp = new MultiThreadedProcessor();

            mtp.LineCountToReportUpdate = 2;
            mtp.ThreadCount = int.Parse(ConfigurationManager.AppSettings.Get("threadCount"));
            mtp.InputFilename = ConfigurationManager.AppSettings.Get("inputFile");
            mtp.ShowProcessingUpdates = false;  //default to no updates until we know which type of records are in the file

            mtp.Process((line) =>
            {
                //skip processing of blank lines
                if (string.IsNullOrWhiteSpace(line))
                    return;

                string[] lineSplits = line.Split(';');

                if (lineSplits.Length > 1 && lineSplits[1].ToLower() == "location" && lineSplits.Length == 3)
                {
                    //process it as a Location setting

                    mtp.ShowProcessingUpdates = true;

                    var location = lineSplits[2];

                    UpdateUserLocation(lineSplits[0], location);


                }
                else if (lineSplits.Length > 1 && lineSplits[1].ToLower() == "location" && lineSplits.Length == 2)
                {
                    //process it as a Print of Location

                    mtp.ShowProcessingUpdates = false;

                    PrintUserLocation(lineSplits[0]);


                }
                else if (lineSplits.Length > 1)
                {
                    //process it as a basic license assignment

                    mtp.ShowProcessingUpdates = true;

                    var userUpn = lineSplits[0];
                    var sku = lineSplits[1].ToLower();
                    var servicePlans = lineSplits[2].ToLower().Split(',');

                    //Console.WriteLine("userUpn:{0}", userUpn);
                    //Console.WriteLine("sku:{0}", sku);
                    //Console.WriteLine("service plans:{0}", string.Join(",", servicePlans));

                    UpdateUserLicense(userUpn, sku, servicePlans);
                }
                else
                {
                    //don't show updates since we are printing output on each line
                    mtp.ShowProcessingUpdates = false;

                    PrintUserLicense(lineSplits[0]);
                }

            });



        }

        private static void UpdateUserLicense(string userUpn, string skuName, string[] enableServicePlans)
        {
            User user = null;
            List<IUser> retrievedUsers = adClient.Users
                    .Where(u => u.UserPrincipalName.Equals(userUpn))
                    .ExecuteAsync().Result.CurrentPage.ToList();

            if (retrievedUsers.Count == 0)
                throw new Exception(string.Format("UPN {0} could not be found", userUpn));

            user = (User)retrievedUsers.First();

            Guid? skuId = GetSkuId(skuName);

            List<Guid> newDisableServicePlans =
                    GetServicePlans(skuName).Where(sp => !enableServicePlans.Contains(sp.ServicePlanName.ToLower()) 
                                                                                        && enableServicePlans.Length > 0 
                                                                                        && sp.ServicePlanId.HasValue)
                                                                             .Select(sp => sp.ServicePlanId.Value)
                        .ToList();

            //if the sku is already assigned to the account, need to figure the intersection of disableds
            if (user.AssignedLicenses.Count(l => l.SkuId == skuId) > 0)
            {
                //need interections of disableds between the user and what would be disabled if all but the enableServicesPlans were disabled
                // get list of inverse of enableServicePlans
                newDisableServicePlans =
                    GetServicePlans(skuName).Where(sp => !enableServicePlans.Contains(sp.ServicePlanName.ToLower()) && enableServicePlans.Length > 0 && sp.ServicePlanId.HasValue)
                                                            .Select(sp => sp.ServicePlanId.Value)
                        .Intersect(
                            user.AssignedLicenses.Where(l => l.SkuId == skuId).SelectMany(l => l.DisabledPlans)).ToList();

            }


            AssignedLicense addLicense = new AssignedLicense() {SkuId = GetSkuId(skuName)};
            addLicense.DisabledPlans = newDisableServicePlans;

            IList<AssignedLicense> licensesToAdd = new[] {addLicense};
            IList<Guid> licensesToRemove = new Guid[] { };

            try
            {
                user.AssignLicenseAsync(licensesToAdd, licensesToRemove).Wait();
            }
            catch (Exception ex)
            {
                throw new Exception(string.Format("User License not updated successfully, {0}", userUpn), ex);
            }


        }

        private static List<ISubscribedSku> _subscribedSkus;


        private static Guid? GetSkuId(string skuPartNumber)
        {
            //helpful to have a lock around this but not required, if we get it multiple times not a huge deal
            if (_subscribedSkus == null)
            {
                _subscribedSkus = adClient.SubscribedSkus.ExecuteAsync().Result.CurrentPage.ToList();
            }

            var sku = _subscribedSkus.First(s => s.SkuPartNumber.ToLower() == skuPartNumber);

            return sku.SkuId;
        }

        private static string GetSkuPartNumber(Guid skuId)
        {
            //helpful to have a lock around this but not required, if we get it multiple times not a huge deal
            if (_subscribedSkus == null)
            {
                _subscribedSkus = adClient.SubscribedSkus.ExecuteAsync().Result.CurrentPage.ToList();
            }

            var sku = _subscribedSkus.First(s => s.SkuId == skuId);

            return sku.SkuPartNumber;
        }



        private static IEnumerable<Microsoft.Azure.ActiveDirectory.GraphClient.ServicePlanInfo> GetServicePlans(string skupartnumber)
        {
            //helpful to have a lock around this but not required, if we get it multiple times not a huge deal
            if (_subscribedSkus == null)
            {
                _subscribedSkus = adClient.SubscribedSkus.ExecuteAsync().Result.CurrentPage.ToList();
            }

            var sku = _subscribedSkus.First(s => s.SkuPartNumber.ToLower() == skupartnumber);

            return sku.ServicePlans;
        }



        private static User PrintUserLicense(string upn)
        {
            User user = null;
            List<IUser> retrievedUsers = adClient.Users
                    .Where(u => u.UserPrincipalName.Equals(upn))
                    .ExecuteAsync().Result.CurrentPage.ToList();

            if (retrievedUsers.Count == 0)
                return user;

            user = (User)retrievedUsers.First();

            List<string> disabledPlans = new List<string>();

            Console.WriteLine("User: {0}", upn);
            foreach (var assignedLicense in user.AssignedLicenses)
            {
                Console.WriteLine(" AssignedLicense: {0} {1}", assignedLicense.SkuId, GetSkuPartNumber(assignedLicense.SkuId.Value));

                foreach (var disabledPlan in assignedLicense.DisabledPlans)
                {
                    //Console.WriteLine("  DisabledPlan: {0}", disabledPlan);
                    disabledPlans.Add(disabledPlan.ToString());
                }
            }

            foreach (var assignedPlan in user.AssignedPlans)
            {
                if (!disabledPlans.Contains(assignedPlan.ServicePlanId.ToString()) && assignedPlan.CapabilityStatus != "Deleted")
                {
                    //Console.WriteLine(" AssignedPlan: {0} {1} {2}", assignedPlan.ServicePlanId, assignedPlan.Service, assignedPlan.CapabilityStatus);
                    Console.WriteLine(" AssignedPlan: {0} {1}", assignedPlan.ServicePlanId, assignedPlan.Service);
                }
            }

            return user;
        }

        private static void PrintSubscribedSkus(ActiveDirectoryClient client)
        {
            IPagedCollection<ISubscribedSku> skus = client.SubscribedSkus.ExecuteAsync().Result;

            foreach (ISubscribedSku sku in skus.CurrentPage.ToList())
            {
                Console.WriteLine(" SkuId: {0}", sku.SkuId);
                Console.WriteLine(" SkuPartNumber: {0}", sku.SkuPartNumber);
                Console.WriteLine(" ConsumedUnits: {0}", sku.ConsumedUnits);
                Console.WriteLine(" CapabilityStatus: {0}", sku.CapabilityStatus);
                Console.WriteLine(" PrepaidUnits.Enabled: {0}", sku.PrepaidUnits.Enabled);
                Console.WriteLine(" PrepaidUnits.Suspended: {0}", sku.PrepaidUnits.Suspended);
                Console.WriteLine(" PrepaidUnits.Warning: {0}", sku.PrepaidUnits.Warning);

                foreach (var servicePlan in sku.ServicePlans)
                {
                    Console.WriteLine("  ServicePlan: {0} {1}", servicePlan.ServicePlanId, servicePlan.ServicePlanName);
                }

                Console.WriteLine("");
            }
        }



        private static void UpdateUserLocation(string userUpn, string location)
        {
            User user = null;
            List<IUser> retrievedUsers = adClient.Users
                    .Where(u => u.UserPrincipalName.Equals(userUpn))
                    .ExecuteAsync().Result.CurrentPage.ToList();

            if (retrievedUsers.Count == 0)
                throw new Exception(string.Format("UPN {0} could not be found", userUpn));

            user = (User)retrievedUsers.First();

            user.UsageLocation = location;


            try
            {
                user.UpdateAsync().Wait();
            }
            catch (Exception ex)
            {
                throw new Exception(string.Format("User License not updated successfully, {0}", userUpn), ex);
            }


        }

        private static User PrintUserLocation(string userUpn)
        {
            User user = null;
            List<IUser> retrievedUsers = adClient.Users
                    .Where(u => u.UserPrincipalName.Equals(userUpn))
                    .ExecuteAsync().Result.CurrentPage.ToList();

            if (retrievedUsers.Count == 0)
                Console.WriteLine("User: {0} could not be found", userUpn);

            user = (User)retrievedUsers.First();

            Console.WriteLine("User: {0}", userUpn);
            Console.WriteLine(" UsageLocation: {0}", user.UsageLocation);

            return user;
        }

    }
}
